/* /* * RGB(W) LED IM Dendrite module (for ESP8266) * Created for Interplaymedium™ project (https://interplaymedium.org) * Copyright © 2016 Dmitry Shalnov [interplaymedium.org] * Licensed under the Apache License, Version 2.0 */ #include "../../info" #include #include #include #include #include #define VERSION "0.1.4" #define EEPROM_STR_MAX_LEN 16 #define EEPROM_OTHER_MAX_LEN 8 #define EEPROM_STR 0 #define EEPROM_STR_FLAG 17 // watch overlap: EEPROM_STR_MAX_LEN #define EEPROM_R 18 #define EEPROM_G 19 #define EEPROM_B 20 #define EEPROM_W 21 #define EEPROM_ROT_H 22 #define EEPROM_ROT_L 23 // -------------------- PWM settings ---------------------------------------------------- extern "C"{ #include "pwm.h" // Includes of Expressif SDK } #define LED1 0 #define LED2 2 #define LED3 3 #define LED4 1 #define PWM_CHANNELS 4 #define PWMSTEPS 255 #define PWM_PERIOD 5000 // Period of PWM frequency, SDK default: 5000 -> * 200ns ^= 1 kHz unsigned int CIEL8[ PWMSTEPS ]; int R=0, G=0, B=0, W=0; unsigned int Rnew=0, Gnew=0, Bnew=0, Wnew=0; unsigned int rotateDelay = 0; int signR = 1, signG = 1, signB = 1, signW = 1; unsigned int eTimer = 0; uint32 pwm_duty_init[PWM_CHANNELS]; // PWM initial duty: OFF by default uint32 io_info[PWM_CHANNELS][3] = { // MUX, FUNC, PIN // {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5, 5}, // D1 // {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4, 4}, // D2 {PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0, LED1}, // D3 0 {PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2, LED2}, // D4 2 {PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3, LED3}, // RX 3 {PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1, LED4}, // TX 1 // {PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14}, // D5 // {PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12}, // D6 // {PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13}, // D7 // {PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 ,15}, // D8 // D0 - not have PWM :-( 16 }; // ------------------- server settings -------------------------------------------------- #define HOST_DEFAULT "im_rgb5" char host[ EEPROM_STR_MAX_LEN ]; const char* ssid = IM_WIFI_SSID; const char* password = IM_WIFI_PASS; ESP8266WebServer server(80); String HTTPresp; // ----------------- change current R G B (W) to another RGBW set ---------------------- void changeRGBto( int newR, int newG, int newB, int newW ){ float stepR, stepG, stepB, stepW; int R2, G2, B2, W2; /* // int R, G, B, W; // R = EEPROM.read( 0 ); // G = EEPROM.read( 1 ); // B = EEPROM.read( 2 ); // W = EEPROM.read( 3 ); */ stepR = (newR - R) / (float)PWMSTEPS; stepG = (newG - G) / (float)PWMSTEPS; stepB = (newB - B) / (float)PWMSTEPS; stepW = (newW - W) / (float)PWMSTEPS; for ( unsigned int a = 0; a < PWMSTEPS; a++ ){ R2 = R + round( stepR * (float)a ); G2 = G + round( stepG * (float)a ); B2 = B + round( stepB * (float)a ); W2 = W + round( stepW * (float)a ); pwm_set_duty( CIEL8[R2], 0 ); pwm_set_duty( CIEL8[G2], 1 ); pwm_set_duty( CIEL8[B2], 2 ); pwm_set_duty( CIEL8[W2], 3 ); pwm_start(); // commit delay( 500 / PWMSTEPS ); } } // ----------- explode for selected substring ----------- String expld( String str, unsigned int numb, char delimiter ){ unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0; lng = str.length(); for ( a = 0; a < lng; a++ ){ if ( str.charAt( a ) == delimiter ) { p2 = p1; p1 = a; if ( cnt == numb ) break; cnt ++; } } if ( a == lng ) { p2 = p1; p1 = lng; } if ( numb > 0 ) p2 ++; return str.substring(p2, p1); } unsigned char URIHasArg( String str, String arg ){ unsigned char a = 0, delimiterCnt = 0, lng = str.length(); for ( a = 0; a < lng; a++ ){ if ( str.charAt( a ) == '/' ) delimiterCnt++; } for ( a=0; a < 10; a++ ){ if ( arg.equals( expld( str, a, '/' ) ) ) return a; } return 0; } // ---------------- EEPROM String r/w ------------------- void EEPROMStrRead( unsigned char addr, char * str ){ for (unsigned char a = addr; a < EEPROM_STR_MAX_LEN; a++ ){ str[ a ] = EEPROM.read( a ); if (str[ a ] == 0) break; } } void EEPROMStrWrite( unsigned char addr, char * str ){ unsigned char a = 0; for (a = addr; a < EEPROM_STR_MAX_LEN; a++ ){ EEPROM.write( a, str[ a ] ); if (str[ a ] == 0) break; } EEPROM.write( a, 0 ); } // ---------------- misc --------------------------------- int str2HEX(const String str) { return strtol( str.c_str(), 0, 16 ); } void blink( unsigned char times ){ for (unsigned char a = 0; a < times; a++ ){ digitalWrite(LED4, HIGH); delay (50); digitalWrite(LED4, LOW); delay (50); } } // --------------- Init -------------------------------- void setup(void) { HTTPresp.reserve(800); // lenght of Help message generally // calculate lookup array for (unsigned int a = 1; a < PWMSTEPS; a++) CIEL8[ a ] = round( pow( PWM_PERIOD, (double)a / (PWMSTEPS-1) ) ); // calculate exponential lookup array for PWM CIEL8[ 0 ] = 0; // init LED pins pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); pinMode(LED3, OUTPUT); pinMode(LED4, OUTPUT); digitalWrite(LED1, LOW); digitalWrite(LED2, LOW); digitalWrite(LED3, LOW); digitalWrite(LED4, LOW); // PWM inti for (uint8_t channel = 0; channel < PWM_CHANNELS; channel++) pwm_duty_init[channel] = 0; // Initial duty -> all off uint32_t period = PWM_PERIOD; pwm_init(period, pwm_duty_init, PWM_CHANNELS, io_info); pwm_start(); // Serial init #if SERIAL == 1 Serial.begin(115200); Serial.println(); Serial.println("Booting Sketch...0"); #endif // mDNS init // deviceURI.reserve(7); // deviceURI = "im" + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00 EEPROM.begin( EEPROM_STR_MAX_LEN + EEPROM_OTHER_MAX_LEN ); if ( EEPROM.read( EEPROM_STR_FLAG ) == 1 ){ EEPROMStrRead( EEPROM_STR, host ); } else { strcpy( host, HOST_DEFAULT ); // default URI and host name } WiFi.hostname( host ); // WiFi.softAP(APssid, APpassword); // WiFi.mode(WIFI_AP); // WiFi.mode(WIFI_AP_STA); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() == WL_CONNECTED) { // MDNS.begin( deviceURI.c_str() ); MDNS.begin( host ); // default server.onNotFound( []() { server.sendHeader("Connection", "close"); unsigned int RGBW = 0xff; String param = ""; String command = ""; // parse plain URI arguments TODO: not sure whether we need it, check Plan 9 specifications /* param = server.arg("rgbw"); if ( param == "" ) { param = URIHasArg( server.uri(), "rgbw" ); } */ // color change if ( server.hasArg("rgbw") ){ // || param != 0 param = server.arg("rgbw"); Rnew = str2HEX( param.substring(0, 2) ); Gnew = str2HEX( param.substring(2, 4) ); Bnew = str2HEX( param.substring(4, 6) ); Wnew = str2HEX( param.substring(6, 8) ); changeRGBto( Rnew, Gnew, Bnew, Wnew ); R = Rnew; G = Gnew; B = Bnew; W = Wnew; } // rotate if ( server.hasArg("rotate") ){ rotateDelay = str2HEX( server.arg("rotate") ); } // rename host if ( server.hasArg("rename") ) { EEPROMStrWrite( EEPROM_STR, (char *)server.arg("rename").c_str() ); EEPROM.write( EEPROM_STR_FLAG, 1 ); EEPROM.commit(); server.sendHeader("Connection", "close"); server.send_P(200, "text/plain", PSTR("Done. System rebooting...\n") ); delay(500); ESP.restart(); } // ------ HTTPresp = "host: " + String(host) + ".lan" + "\n"; HTTPresp += "URI: " + server.uri() + "\n"; // HTTPresp += "Arg RGBW: " + String( URIHasArg( server.uri(), "test2" ) ) + "\n"; // URI contains /test2/ // HTTPresp += "Arg RGBW: " + server.arg("aaaa") + "\n"; // GET or POST params has "aaaa" HTTPresp += "rotate: " + String(rotateDelay) + "\n"; HTTPresp += "rgbw: " + String(R) + " " + String(G) + " " + String(B) + " " + String(W) + "\n"; server.send(200, "text/plain", HTTPresp); }); // test EEPROM string server.on("/eeprom", HTTP_GET, []() { char * testStr2 = "asdfghjkl12345"; server.setContentLength(CONTENT_LENGTH_UNKNOWN); server.send(200, "text/plain", ""); EEPROMStrRead( EEPROM_STR, testStr2 ); server.sendContent( testStr2 ); server.sendContent( "\n" ); }); // help server.on("/help", HTTP_GET, []() { HTTPresp = "Interplay Medium ESP8266 LED RGB(W) PWM Controller. Version: " + String(VERSION) + "\n"; HTTPresp += "Written by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later . \n\n"; HTTPresp += " rgbw Red Green Blue and White components in hexadecimal form (e.g. ff00ff00)\n"; HTTPresp += " rotate Delay of components rotation FX in hexadecimal (e.g. ffff), set 0 to stop\n"; HTTPresp += " update Wireless update of firmware (see example below)\n"; HTTPresp += " help Send this help\n\n"; HTTPresp += "Usage: curl http://" + String(host) + ".lan [--data \"rgbw=\"] [--data \"rotate=\"] \n"; HTTPresp += " curl http://" + String(host) + ".lan[/rgbw/][/rotate/] \n"; HTTPresp += "Examples: curl http://" + String(host) + ".lan/?rgbw=00ff00ff&rotate=ff \n"; HTTPresp += " curl -F image=@RGBW_Controller.bin " + String(host) + ".lan/update \n"; server.sendHeader("Connection", "close"); server.send( 200, "text/plain", HTTPresp ); }); // OTA update server.on("/update", HTTP_POST, []() { server.sendHeader("Connection", "close"); server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); ESP.restart(); }, []() { HTTPUpload& upload = server.upload(); if (upload.status == UPLOAD_FILE_START) { rotateDelay = 0; blink( 3 ); #if SERIAL == 1 Serial.setDebugOutput(true); #endif WiFiUDP::stopAll(); #if SERIAL == 1 Serial.printf("Update: %s\n", upload.filename.c_str()); #endif uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; if (!Update.begin(maxSketchSpace)) { //start with max available size #if SERIAL == 1 Update.printError(Serial); #endif } } else if (upload.status == UPLOAD_FILE_WRITE) { if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { #if SERIAL == 1 Update.printError(Serial); #endif } } else if (upload.status == UPLOAD_FILE_END) { if (Update.end(true)) { //true to set the size to the current progress server.sendHeader("Connection", "close"); server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") ); #if SERIAL == 1 Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize); #endif } else { server.sendHeader("Connection", "close"); server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") ); #if SERIAL == 1 Update.printError(Serial); #endif } #if SERIAL == 1 Serial.setDebugOutput(false); #endif } yield(); }); // start server server.begin(); MDNS.addService("http", "tcp", 80); #if SERIAL == 1 Serial.printf("Ready! Open http://%s.local in your browser\n", host); #endif } else { #if SERIAL == 1 Serial.println("WiFi Failed"); #endif } } void loop(void) { server.handleClient(); MDNS.update(); eTimer ++; if ( eTimer >= rotateDelay && rotateDelay != 0 ) { eTimer = 0; R = R + signR; G = G + signG; B = B + signB; W = W + signW; if ( R >= PWMSTEPS-1 ) { signR = -1; R = PWMSTEPS-1; } if ( G >= PWMSTEPS-1 ) { signG = -1; G = PWMSTEPS-1; } if ( B >= PWMSTEPS-1 ) { signB = -1; B = PWMSTEPS-1; } if ( W >= PWMSTEPS-1 ) { signW = -1; W = PWMSTEPS-1; } if ( R < 2 ) { signR = 1; R = 2; } if ( G < 2 ) { signG = 1; G = 2; } if ( B < 2 ) { signB = 1; B = 2; } if ( W < 2 ) { signW = 1; W = 2; } pwm_set_duty( CIEL8[R], 0 ); pwm_set_duty( CIEL8[G], 1 ); pwm_set_duty( CIEL8[B], 2 ); pwm_set_duty( CIEL8[W], 3 ); pwm_start(); // commit } }